package com.example.zz.example.handler;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.example.zz.example.R;

import java.lang.ref.WeakReference;

/**
 * handler是生产消费者模式
 * handler作用：1、线程消息切换；2、延时执行或者循环执行（同线程或者不同线程）
 * handler缺点：内存泄漏
 * handler两种写法：写法一、写法二、简化写法，在发送延迟消息时都会导致内存泄漏，写法二使用匿名内部内在编写时就会提示内存泄漏风险
 *
 * 解决内存泄漏：
 * 1、在destroy中将handler的所有消息全部清空，没有延时消息之后就不会导致activity释放不了，
 * 下面mHandler2实测，removeCallbacksAndMessages在destroy中调用内存不泄漏，不调用时，延时消息会导致内存泄漏
 * 2、使用静态handler，参考mHandler4，静态的handler对HandlerActivity是弱引用，不影响activity的回收
 *
 * handler执行流程、原理：
 * handler四要素：handler、message、messageQueue、Looper；两个线程进行通信，Looper在一个线程开启循环（Looper.prepare->new Handler->Looper.loop,必须顺序执行），
 * handler在另一个线程中将message消息发送到messageQueue中，Looper中的循环就可以从messageQueue中取到按时间排列的消息，回调给自己的线程去进行处理。
 *
 * 以线程一、线程二为例，
 * 1、线程一中需要先调用Looper.prepare()。作用是新建一个new Looper，并将这个looper放入sThreadLocal的ThreadLocalMap中，
 * key是当前线程值，value是looper，一个线程只能有一个looper，prepare时如果检查到已经有looper会抛异常；new Looper时，
 * 会在构造函数中为这个looper新建其对应的messageQueue，所以一个线程对应一个looper，一个looper对应一个messageQueue。
 *  总结 1/创建Looper对象
 *      2/创建MessageQueue对象，并让Looper对象持有
 *      3/让Looper对象持有当前线程
 * 2、新建当前线程的handler。new Handler并复写handleMessage用于接受其他线程发过来的消息；new Handler的构造函数中，通过Looper.myLooper()方法从
 * sThreadLocal -> ThreadLocalMap中将当前线程对应的messageQueue取出作为成员变量，可以知道，new Handler之前如果没有prepare，那就取不到队列，就会报错
 * 3、开启轮询Looper.loop()。在loop方法中，会通过Looper.myLooper()方法从sThreadLocal -> ThreadLocalMap中将当前线程对应的messageQueue取出，然后开启无线循环，
 * 在循环中通过调用messageQueue.next取队列中的消息进行处理，没有消息时，next方法会阻塞，当前线程会挂起；有消息时，取出的消息的target成员变量，再调用target.dispatchMessage将消息分发下去
 * message中的这个target实际就是发送该消息的handler，所以一个线程新建多个handler并发送消息时，每次发送消息时，消息会将该发送handler包装成target，便于接受消息时做区分。
 * Looper.loop()一旦调用后，会始终处于循环中，后面的代码不会执行了，所以上面三步一步套一步，不能修改顺序，Looper.loop()要最后调用。
 * 4、在线程二中发消息给线程一。通过第2步中新建的线程一的handler，在线程二中调用发送消息，发送后会将消息放入handler持有的looper对应的messageQueue中，
 * 步骤3中的循环就会在messageQueue.next收到并分发给对应的handler的handleMessage
 * 5、延时消息流程。handler通过post或者send发送消息时，都会走到sendMessageAtTime中，两个参数，一个参数是消息，第二个参数是开机时间（不含休眠）+延迟时间；
 * 再调用enqueueMessage方法将消息加入到messageQueue中，方法中先将第二个参数赋值给msg.when，再将当前的msg与队列中的msg比较when大小，按照从小到大的顺序插入进入
 * 取出时就会按照顺序取出
 * 6、Looper.loop()调用之后，就会开启无限for循环，在无限循环中调用queue.next()方法，在MessageQueue类中的next方法中调用nativePollOnce进入休眠，
 * 这个native方法中的参数nextPollTimeoutMillis是，要休眠的时长，-1表示没有事件，一直休眠；如果有个10s延时消息，就会在nativePollOnce中进入休眠，
 * 休眠的过程中有新消息进来会存入MessageQueue中，如果是非延时消息，就会在消息存入队列的方法queueMessage中调用nativeWake进行唤醒执行，如果是延时消息，就会插入队列并重新计算下次唤醒时间
 * 参考：https://www.jianshu.com/p/592fb6bb69fa
 * 参考：https://www.jianshu.com/p/9631eebadd5c
 *
 * 面试题：
 * 为什么要用主线程更新UI不通过子线程更新，通过多个子线程更新ui时加锁，这样是线程安全的，不也可以实现吗？
     * 避免多个子线程更新界面，导致界面混乱。加锁会导致性能下降，UI对响应时间要求非常高，加锁无法保证绘制效率
 * 子线程不能更新ui吗
     * 子线程也是可以更新，如SurfaceView；主线程不能更新UI只是谷歌在framework层添加的一个检查机制强制要求在更新界面时线程是主线程，
     * 检查方式是更新UI前都会调用checkTread()，并在其中检查，非主线程就会抛异常，checkThread会在requestLayout等方法中进行检查；
     * 避开这样的场景就可以实现子线程更新，如onCreate中新建子线程更新textview的值，此时textview还没加入ViewTree中，不会抛异常
 * 为什么主线程可以直接new Handler，子线程可以吗，子线程怎么使用handler
    * 可以，new Handler使用的前提要先调用Looper.prepare()，虽然我们自己没有调用，但是主线程创建时在ActivityThread中已经被调用了，
    * 主线程要自己调用Looper.prepare的原因是View绘制事件等都是通过主线程的 Handler 来进行调度的。子线程new handler时，必须自己调用，调用流程为上面的1、2、3、步
 * 主线程调用Looper.prepare会怎么样
    * 会崩溃，因为主线程在activityThread中的main函数中已经给app初始化过了，而Looper.prerare只能初始化一次，重复初始化会抛异常
 * handler是looper无限循环，为什么没有阻塞主线程
    *looper无线循环时，会调用nativePollOnce，在native层最后调用epoll_wait进行休眠；当有新事件进来时，再唤醒处理新事件
 * 一个线程有有几个looper，如何保证looper个数，有几个messageQueue，可以有几个handler，looper和handler对应关系是什么
    * 一个线程只有一个looper和一个messageQueue，looper是在静态方法Looper.prepare时新建的，并且会进行检查，如果已经有了，就会抛异常；可以有多个handler，handler与looper是多对一的关系
 * 子线程可以创建looper吗，子线程looper和主线程looper区别？
    *子线程不会自己创建looper，需要自己通过Looper.prepare进行创建；主线程的looper在app启动时，ActivityThread、
 * 一个线程中handler对应looper是一对多还是一对一
    * handler对应looper是多对一
 * handler无限循环，没有消息时是什么状态
    * 没有消息时，Looper.loop()方法中的无限for循环会调用queue.next方法，最终会调用到nativePollOnce，由于没有消息，参数为-1，进入长时间休眠
 * messageQueue在没有消息的时候会阻塞，如何恢复
    * 加入新消息后，如果是非延时消息，会在MessageQueue中的enqueueMessage中通过nativeWake唤醒
 * handler的不同发送message的方法有哪些，最终调用的是什么方法
    * 发送消息方法有post、postDelay、sendEmptyMessage、sendEmptyMessageDelay、sendEmptyMessageAtTime、sendMessage、sendMessageDelay、sendMessageAtTime。
    * 这些方法都是通过计算delay的时间点，最后调用sendMessageAtTime，再调用Handler.enqueueMessage，在调用MessageQueue.enqueueMessage.
 * message有哪些创建方式，区别是什么，那种好
    * new Message()，handler.obtainMessage，Message.obtainMessage方法获取，其中第一个每次都会新建，会导致内存抖动；第二个最终还是调用的第三个，
    * Message.obtainMessage方法会从sPool消息池中取出，如果消息池中没有就会新建，新建的消息会放入消息池中；注意：自己new的消息是不会进入消息池的
 * handler延时消息执行计时是否准确，不准确场景，如何保证准确
    * 不准确，当上一个消息存在耗时任务时，会占用延时任务的时机。如：发送两个任务，一个1s延时，一个3s延时，当1s延时任务运行花费3s时，第二个任务只能在第4s得到执行
    * 那么如何准确执行延时任务呢，分两种情况：1、handler在子线程，可以新开一个子线程运行，如开一个HandlerThread为一个专门的唯一的延时消息服务。
    * 2、handler在主线程，主线程出现耗时是明显的bug，会导致界面卡顿，严重时会导致anr；所以需要把耗时任务放到子线程运行，再把结果返回主线程处理
 * handler从多个不同子线程同时发消息，如何保证安全的
    * 不通过子线程消息最终都会放入到消息队列中，而消息队列在放入和取出的时候都是加锁的，所以是通过加锁保证安全
 * handler的postDelay后，消息队列发生什么变化、
    * postDelay之后，最终都会调用sendMessageAtTime，将消息按照时间顺序插入到消息队列中
 * Android中，有哪些是基于Handler来实现通信的？
    * App的运行、更新UI、AsyncTask、Glide、RxJava等
 * 处理Handler消息，是在哪个线程？一定是创建Handler的线程么？
    * handler使用无参构造时，默认是取的当前线程looper，并在looper中循环处理消息；handler有参初始化，传入looper时，就是传入的looper对应的线程中进行处理
    * 如new Handler(handlerThread.getLooper()) new Handler(Looper.getMainLooper())，第一个就是新建的handlerThread子线程的handler，
    * 由于HandlerThread包装调用了looper.prepare，所以可以getLooper，放入handler中使用，而自己new Thread新建的线程要自己调用Looper.prepare才能有looper，
    * 所以new Handler(thread.getLooper())是不行的
 * 当MessageQueue没有消息时，它的next方法是阻塞的，会导致App ANR么？或者说andler里面的nativePollOnce为什么不会ANR？
    * 不会，anr的原因是事件得不到处理，而next中nativePollOnce阻塞休眠时，意味着当前没有信息需要处理，如果此时有事件进来，就会马上唤醒并执行事件，所以不会出现anr
 * 子线程中可以使用Toast么？
    * 违反了android设计原则，但是可以实现，在子线程中调用Looper.prepare->弹toast ->Looper.loop，就可以。
    * 为什么调用prepare就可以？因为toast使用了handler，而handler需要先调用Looper.prepare，再调用Looper.loop才能正常运行
    * 为什么Toast要用handler？toast多条并且有show/hide等操作，设计的就是按照handler的messagequeue的队列进行维护和处理事件，这样能保证刷新顺序和处理
 * Looper.loop()是死循环，可以停止么？子线程中开启了looper死循环，子线程如何释放
    * Loope.loop使用来接受事件的，应用生命过程中，loop一直处于休眠和唤醒处理事件两个状态，当应用不需要接受事件退出时，loop就停止了；
    * 子线程调用quite或者quitSafely，子线程在退出过程中会清空消息队列，looper.next获取不到消息，for循环就会退出；
 * Handler内存泄露怎么解决？
    * 1、activity退出时，清空持有的所有消息；2、将handler改为静态，并通过弱引用持有activity，gc时activity会被回收，注意handler中要判空，因为activity可能已被回收
 * handler的生产者消费者模式是指的什么
    * handler运行时，是发送消息和处理消息，通过handler发送消息就是生产者，handler收到消息后处理消息就是消费者
 * handler中ThreadLocal是什么作用
    * 用来维护不同线程与对应的loop的关系，在ThreadLocal中有ThreadLocalMap，存储的就是线程和对应looper的键值对
 * handler为什么使用管道
    * 线程之间内存是共享的，管道主要工作是用于通知另一个线程的，这便是最核心的作用。
    * 在Android 6.0及以前的版本使⽤管道与epoll来完成Looper的休眠与唤醒的。
    * 在Android 6.0及以后的新版本中使⽤的是eventfd与epoll来完成Looper的休眠与唤醒的。
 */

public class HandlerActivity extends AppCompatActivity {

    private int n1 = 0;
    private int n2 = 0;

    //常规写法一：
    private Handler mHandler1 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            button1_num.setText("" + n1);
            switch (msg.what) {
                case 22:
                    int arg1 = msg.arg1;
                    String obj = (String) msg.obj;
                    Bundle data = msg.getData();
                    int num = data.getInt("num");
                    String name = data.getString("name");
                    String s = " " + arg1 + " " + obj + " " + num + " " + name;
                    button1_num.setText(s);
            }
            return false;
        }
    });

    //常规写法二：
    private Handler mHandler2 = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            button2_num.setText("" + n2);
            n2++;
            mHandler2.sendEmptyMessageDelayed(0, 100);
        }
    };

    //简化写法：适用于在子线程随时更新界面
    private Handler mHandler3 = new Handler(Looper.getMainLooper());

    //谷歌标准版内存不泄漏写法，（上面的三种都可能出现内存泄漏）
    private final MyHandler mHandler4 = new MyHandler(this);

    private static class MyHandler extends Handler {
        private final WeakReference<HandlerActivity> mActivity;

        public MyHandler(HandlerActivity activity) {
            mActivity = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = mActivity.get();
            if (activity != null) {
                activity.button4_num.setText("不会内存泄漏handler发送的消息");
            }
        }
    }

    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() {
            //静态方法内无法取到HandlerActivity的变量，这样使用不好刷新界面，不推荐
        }
    };

    Button button1;
    Button button2;
    Button button3;
    Button button4;

    TextView button1_num;
    TextView button2_num;
    TextView button3_num;
    TextView button4_num;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        button1 = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);
        button3 = (Button) findViewById(R.id.button3);
        button4 = (Button) findViewById(R.id.button4);

        button1_num = (TextView) findViewById(R.id.button1_num);
        button2_num = (TextView) findViewById(R.id.button2_num);
        button3_num = (TextView) findViewById(R.id.button3_num);
        button4_num = (TextView) findViewById(R.id.button4_num);

        //线程切换，在子线程中计算，发送到主线程进行更新界面
        button1.setOnClickListener(v -> {
            new Thread(() -> {
                //在子线程计算
                n1 = n1 + 1;

                //更新方式一：通过Runnable 进行主线程更新
                mHandler1.post(() -> {
                    button1_num.setText("" + n1);
                });

                //更新方式二：发送empty消息进行主线程更新
                n1 = n1 + 2;
                mHandler1.sendEmptyMessage(11);

                //更新方式三：发送消息体区分不同消息进行更新
                Message message = mHandler1.obtainMessage();
                message.what = 22;  //消息id
                message.arg1 = 123;  //传递数字
                message.obj = "传递字符串";  //传递字符串
                Bundle bundle = new Bundle();   //传递对象
                bundle.putInt("num", 10);
                bundle.putString("name", "zhang");
                message.setData(bundle);
                mHandler1.sendMessage(message);
            }).start();
        });

        //延时执行或者循环执行（同线程不同线程均可）
        button2.setOnClickListener(v -> {
            mHandler2.sendEmptyMessageDelayed(0, 10000);
        });

        //简化版使用
        button3.setOnClickListener(v -> {
            mHandler3.post(() -> {
                button3_num.setText("简化版");
            });
        });

        //谷歌标准内存无泄漏版本
        button4.setOnClickListener(v -> {
            mHandler4.sendEmptyMessageDelayed(0, 3000);
            mHandler4.postDelayed(sRunnable, 10000);
        });

        //HandlerThread
        HandlerThread handlerThread = new HandlerThread(""){
            @Override
            public void run() {
                //子线程执行
                super.run();
            }
        };
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
            }
        };
        handlerThread.quitSafely();
        ChildThread childThread = new ChildThread();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler2.removeCallbacksAndMessages(null);
    }


    class ChildThread extends Thread {
        public Handler mChildHandler;

        @Override
        public void run() {
            Looper.prepare();
            mChildHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
            Looper.loop();
        }
    }
}
